/*
 * $Id$
 *
 * Copyright (c) 2006, 2007, 2008 Prabhu Gurumurthy <pgurumur@gmail.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include "egpd.h"
#include <net/if_dl.h>

#define ROUND_UP(ix) \
   ((ix) > 0 ? (1 + (((ix) - 1) | (sizeof(long) - 1))) : sizeof(long))
#define ADVANCE(ix, jy) (ix += ROUND_UP((jy)->sa_len))

void
egp_socket()
{
   struct egpd_group *group;
   int ix = 0;
   LIST_FOREACH(group, &conf->groups, entry) {
      for (ix = 0; ix < group->peercnt; ix++) {
         get_route(group->peers[ix]);
      }
   }
}

char *
satostring(struct sockaddr *sa)
{
   char *retval = NULL;
   if (sa) {
      struct ipaddr ip;
      memset(&ip, 0, sizeof(struct ipaddr));
      ip.family = sa->sa_family;

      if (sa->sa_family == AF_INET) {
         struct sockaddr_in *in4 = (struct sockaddr_in *)sa;
         memcpy(&in4->sin_addr, &(ip.address.addr4), sizeof(struct in_addr));
      }
      else if(sa->sa_family == AF_INET6) {
         struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)sa;
         memcpy(&in6->sin6_addr, &(ip.address.addr6), sizeof(struct in6_addr));
      }
      retval = addr2string(&ip);
   }

   return retval;
}

void
get_route(struct egpd_peer peer)
{
   struct {
      struct rt_msghdr rthdr;
      char rtspace[512];
   } rtmesg;

   int flags = 0;
   int length = 0;
   int rtm_addrs = 0;
   char *cp = rtmesg.rtspace;
   struct rt_metrics rt_metrics;
   int tableid = getrtable();
   int sock = socket(PF_ROUTE, SOCK_RAW, AF_UNSPEC);

   flags |= RTF_UP;
   flags |= RTF_HOST;
   flags |= RTF_GATEWAY;
   rtm_addrs |= RTA_DST;

#define NEXT_ADDR(type, addr) \
   if (rtm_addrs & (type)) {  \
      length = ROUND_UP((addr).sa_len); \
      memcpy(cp, &(addr), length);     \
      cp += length;  \
   }

   errno = 0;
   memset(&rtmesg, 0, sizeof(rtmesg));
   memset(&rt_metrics, 0, sizeof(struct rt_metrics));
   rtmesg.rthdr.rtm_type = RTM_GET;
   rtmesg.rthdr.rtm_pid = getpid();
   rtmesg.rthdr.rtm_flags = flags;
   rtmesg.rthdr.rtm_fmask = 0;
   rtmesg.rthdr.rtm_version = RTM_VERSION;
   rtmesg.rthdr.rtm_seq = 0;
   rtmesg.rthdr.rtm_rmx = rt_metrics;
   rtmesg.rthdr.rtm_inits = 0;
   rtmesg.rthdr.rtm_tableid = tableid;
   rtmesg.rthdr.rtm_priority = 0;
   rtmesg.rthdr.rtm_mpls = 0;
   rtmesg.rthdr.rtm_hdrlen = sizeof(rtmesg.rthdr);

   shutdown(sock, 0);
   struct sockaddr *dest = NULL;
   struct sockaddr gate;
   struct sockaddr ifp;

   memset(&gate, 0, sizeof(struct sockaddr));
   memset(&ifp, 0, sizeof(struct sockaddr));
   if (peer.ip.family == AF_INET) {
      struct sockaddr_in sa;
      memset(&sa, 0, sizeof(struct sockaddr_in));
      sa.sin_family = peer.ip.family;
      memcpy(&sa.sin_addr, &peer.ip.address.addr4, sizeof(struct in_addr));
      dest = (struct sockaddr *)&sa;
      dest->sa_len = sizeof(struct sockaddr_in);
      gate.sa_len = sizeof(struct sockaddr_in);
   }
   else if (peer.ip.family == AF_INET6) {
      struct sockaddr_in6 sa;
      memset(&sa, 0, sizeof(struct sockaddr_in6));
      sa.sin6_family = peer.ip.family;
      memcpy(&sa.sin6_addr, &peer.ip.address.addr6, sizeof(struct in6_addr));
      dest = (struct sockaddr *)&sa;
      dest->sa_len = sizeof(struct sockaddr_in6);
      gate.sa_len = sizeof(struct sockaddr_in6);
   }
   ifp.sa_family = AF_LINK;
   ifp.sa_len = sizeof(struct sockaddr_dl);
   rtm_addrs |= RTA_IFP;
   NEXT_ADDR(RTA_DST, *dest);
   NEXT_ADDR(RTA_GATEWAY, gate);
   NEXT_ADDR(RTA_IFP, ifp);
   rtmesg.rthdr.rtm_addrs = rtm_addrs;
   rtmesg.rthdr.rtm_msglen = length = cp - (char *)&rtmesg;
   if (write(sock, &rtmesg, length) != length)
      warning("writing to routing socket");

   do {
      length = read(sock, &rtmesg, sizeof(rtmesg));
      printf("%d\n", length);
   } while (length > 0 && (rtmesg.rthdr.rtm_version != RTM_VERSION || 
            rtmesg.rthdr.rtm_seq != 0 || rtmesg.rthdr.rtm_pid));

   if (rtmesg.rthdr.rtm_errno == 0) {
      printf("length: %d, type: %d, priority: %d\n", rtmesg.rthdr.rtm_msglen,
            rtmesg.rthdr.rtm_type, rtmesg.rthdr.rtm_priority);

      char *ptr = ((char *)&rtmesg.rthdr + rtmesg.rthdr.rtm_hdrlen);
      if (rtmesg.rthdr.rtm_addrs) {
         int ix = 1;
         for (ix = 1; ix; ix <<= 1) {
            if (ix & rtmesg.rthdr.rtm_addrs) {
               struct sockaddr *sa = (struct sockaddr *)ptr;
               switch (ix) {
               case RTA_DST:
                  printf("%s\n", satostring(sa));   
                  break;
               case RTA_GATEWAY:
                  printf("%s\n", satostring(sa));
                  break;
               }
               ADVANCE(ptr, sa);
            }
         }
      }
   }
   close(sock);
}
